//== SAGITTARIUS ============================================================================

//== BEGINNING OF CODE ===============================================================

#include "sagittarius/core/SagittariusCore.h"
#include "sagittarius/backend/llvm/codegen.h"
#include "sagittarius/io/io.h"
#include <stdio.h>
#include <iostream>

namespace Sagittarius {
    
    uint64 gcounter=0;
    
    LLVM_Interface* llvmi;
    
    // -- Constants --
    
    void CodeGen_Types::init_internals() {
        std::vector<llvm::Type*> args;
        
        // f_dump_fun_val
        args.clear();
        args.push_back(pfunval);
        args.push_back(symcode_tp);
        f_dump_fun_val_tp = llvm::FunctionType::get(void_tp, args, false);
        f_dump_fun_val = (llvm::Function*)mod->getOrInsertFunction("__f_dump_fun_val", f_dump_fun_val_tp);
        
        // f_check_fvaltype
        args.clear();
        args.push_back(pfunval);
        f_check_fvaltype_tp = llvm::FunctionType::get(void_tp, args, false);
        f_check_fvaltype = (llvm::Function*)mod->getOrInsertFunction("__f_check_fvaltype", f_check_fvaltype_tp);
        
        // f_list_reserve
        args.clear();
        args.push_back(plistval_tp);
        args.push_back(i64_tp);
        f_list_reserve_tp = llvm::FunctionType::get(void_tp, args, false);
        f_list_reserve = (llvm::Function*)mod->getOrInsertFunction("__f_list_reserve", f_list_reserve_tp);
        
        // f_list_get_by_index
        args.clear();
        args.push_back(plistval_tp);
        args.push_back(i64_tp);
        f_list_get_by_index_tp = llvm::FunctionType::get(void_tp, args, false);
        f_list_get_by_index = (llvm::Function*)mod->getOrInsertFunction("__f_list_get_by_index", f_list_get_by_index_tp);
        
        // f_list_store_by_index
        args.clear();
        args.push_back(plistval_tp);
        args.push_back(pval);
        args.push_back(i64_tp);
        f_list_store_by_index_tp = llvm::FunctionType::get(void_tp, args, false);
        f_list_store_by_index = (llvm::Function*)mod->getOrInsertFunction("__f_list_store_by_index", f_list_store_by_index_tp);
    }
    
    llvm::Constant* CodeGen_Types::getConstVoidPtr(void* p) {
        return llvm::ConstantExpr::getIntToPtr(
                    llvm::ConstantInt::get(lp_tp, (intptr)p),
                    pvoid);
    }
    
    llvm::Constant* CodeGen_Types::getConstPVal(sg_value* v) {
        return llvm::ConstantExpr::getBitCast(getConstVoidPtr(v), pval);
    }
    
    llvm::Constant* CodeGen_Types::getConstPPVal(sg_value** v) {
        llvm::Constant* c;
        c = llvm::ConstantExpr::getBitCast(getConstVoidPtr(v), ppval);
        AT(c->getType() == ppval);
        return c;
    }
    
    llvm::Constant* CodeGen_Types::getConstSym(symcode s) {
        return llvm::ConstantInt::get(symcode_tp, s);
    }
    
    llvm::Value* CodeGen_Types::getPVal(llvm::Value* v) {
        return builder->CreateBitCast(v, pval);
    }
    
    llvm::Constant* CodeGen_Types::getPValNull() {
        return llvm::ConstantExpr::getBitCast(getConstVoidPtr(NULL), pval);
    }
    
    llvm::Value* CodeGen_Types::getPPVal(llvm::Value* v) {
        return builder->CreateBitCast(v, ppval);
    }
    
    llvm::Constant* CodeGen_Types::getUInt64Const(uint64 n) {
        return llvm::ConstantInt::get(i64_tp, n);
    }
    
    llvm::Constant* CodeGen_Types::getInt64Const(int64 n) {
        return llvm::ConstantInt::get(i64_tp, n);
    }
    
    llvm::Value* CodeGen_Types::castToPVal(llvm::Value* obj) {
        return builder->CreateBitCast(obj, pval);
    }
    
    /*llvm::Value* CodeGen_Types::getConstSymbol(sg_name_val* m) {
        builder->CreateBitCast(getConstPVal((sg_value*)m), pname_val_tp);
    }*/
    
    llvm::Value* CodeGen_Types::getConstFunVal(sg_fun_val* f) {
        return llvm::ConstantExpr::getBitCast(getConstPVal((sg_value*)f), pfunval);
    }
    
    void CodeGen_Types::checkFunValTp(llvm::Value* fv) {
        std::vector<llvm::Value*> args;
        AT(fv->getType() == pfunval, "Expected pointer to funval");
        
        args.push_back(fv);
        builder->CreateCall(f_check_fvaltype, args);
    }
    
    llvm::Value* CodeGen_Types::castToFunVal(llvm::Value* obj) {
        // TODO: runtime type checking
        llvm::Value* fv = builder->CreateBitCast(obj, pfunval);;
        checkFunValTp(fv);
        return fv;
    }
    
    void CodeGen_Types::callDumpConstFunVal(sg_fun_val* f) {
        AN(f);
        
        std::vector<llvm::Value*> args;
        
        args.push_back(getConstFunVal(f));
        AN(f->s);
        args.push_back(getConstSym(f->s));
        
//         AT(args.size() == 2);
//         AN(args[0]->getType());
//         AN(args[1]->getType());
//         args[0]->getType()->dump();
//         args[1]->getType()->dump();
        AN(f_dump_fun_val);
        
        builder->CreateCall(f_dump_fun_val, args);
    }
    
    void CodeGen_Types::funVal_StoreFP(sg_fun_val* f, llvm::Value* fp) {
        llvm::Value* fval = getConstFunVal(f);
        builder->CreateStore(builder->CreateBitCast(fp, pvoid), builder->CreateStructGEP(fval, val_header_off+SG_FUN_VAL_F_OFFSET));
        callDumpConstFunVal(f);
    }
    
    llvm::Value* CodeGen_Types::getFunValVd(sg_fun_val* fv) {
        llvm::Value* fval = getConstFunVal(fv);
        return builder->CreateLoad(builder->CreateStructGEP(fval, val_header_off+SG_FUN_VAL_F_OFFSET));
    }
    
    llvm::Value* CodeGen_Types::getFunValInlineVd(llvm::Value* fval) {
        AT(fval->getType() == pfunval, "Expected type funval");
        return builder->CreateLoad(builder->CreateStructGEP(fval, val_header_off+SG_FUN_VAL_F_OFFSET));
    }
    
    llvm::Value* CodeGen_Types::getFunValFPtr(sg_fun_val* fv) {
        SG_WRITE(2, "getFunValFPtr\n");
        llvm::Value* vd = getFunValVd(fv);
        SG_WRITE(2, "  Casting ");
        vd->getType()->dump();
        if(vd->getType()->isPointerTy())
            SG_WRITE(2, " (ptr)");
        else
            SG_WRITE(2, " (not ptr)");
        SG_WRITE(2, " -> ");
        llvm::Type* fpt = llvm::PointerType::get((llvm::FunctionType*)fv->lft, 0);
        fpt->dump();
        if(fpt->isPointerTy())
            SG_WRITE(2, " (ptr)");
        else
            SG_WRITE(2, " (not ptr)");
        return builder->CreateBitCast(vd, fpt);
    }
    
    llvm::Value* CodeGen_Types::getFunValInlineFPtr(llvm::Value* fv, llvm::FunctionType* ft) {
        SG_WRITE(2, "getFunValInlineFPtr\n");
        if(!(fv->getType() == pfunval)) {
            SG_WRITE(2, "Type is:\n");
            fv->getType()->dump();
            SG_WRITE(2, "\n");
        }
        AT(fv->getType() == pfunval, "Expected type funval");
        llvm::Value* vd = getFunValInlineVd(fv);
        SG_WRITE(2, "  Casting ");
        vd->getType()->dump();
        if(vd->getType()->isPointerTy())
            SG_WRITE(2, " (ptr)");
        else
            SG_WRITE(2, " (not ptr)");
        SG_WRITE(2, " -> ");
        llvm::Type* fpt = llvm::PointerType::get(ft, 0);
        fpt->dump();
        if(fpt->isPointerTy())
            SG_WRITE(2, " (ptr)");
        else
            SG_WRITE(2, " (not ptr)");
        return builder->CreateBitCast(vd, fpt);
    }
    
    llvm::Value* CodeGen_Types::getConstNameValPtr(sg_name_val* m) {
        return llvm::ConstantExpr::getBitCast(getConstPVal((sg_value*)m), pname_val_tp);
    }
    
    void CodeGen_Types::createTypes() {
        
        i8_tp = llvm::IntegerType::get(llvm::getGlobalContext(), 8);
        i16_tp = llvm::IntegerType::get(llvm::getGlobalContext(), 16);
        i32_tp = llvm::IntegerType::get(llvm::getGlobalContext(), 32);
        i64_tp = llvm::IntegerType::get(llvm::getGlobalContext(), 64);
        
        void_tp = llvm::Type::getVoidTy(llvm::getGlobalContext());
        pvoid = llvm::PointerType::get(i8_tp,0);
        
        #if SAGITTARIUS_ARCH == SAGITTARIUS_ARCH_64
            lp_tp = i64_tp;
        #elif SAGITTARIUS_ARCH == SAGITTARIUS_ARCH_32
            lp_tp = i32_tp;
        #endif
        
        // symcode type
        symcode_tp = i64_tp;
        
        // -- object header --
        obj_header = getLLVMTypes()->make_obj_header();
        
        // -- sg_value adaptation --
        // type to represent objects
        std::vector<llvm::Type*> obj_fields(obj_header);
        val_header_off = obj_fields.size();
        // structure
        val = llvm::StructType::get(llvm::getGlobalContext(), obj_fields);
        //val->setName("val");
        pval = llvm::PointerType::get(val, 0);
        ppval = llvm::PointerType::get(pval, 0);
        
        // -- sg_name_val adaptation --
        obj_fields = obj_header;
        // symcode
        obj_fields.push_back(symcode_tp);
        // object
        obj_fields.push_back(pvoid);
        // llvm val
        obj_fields.push_back(pvoid);
        name_val_tp = llvm::StructType::get(llvm::getGlobalContext(), obj_fields);
        pname_val_tp = llvm::PointerType::get(name_val_tp,0);
        
        // -- sg_symbol_inst adaptation --
        obj_fields = obj_header;
        // symcode
        obj_fields.push_back(symcode_tp);
        // object
        obj_fields.push_back(pval);
        // llvm val
        obj_fields.push_back(pvoid);
        symbol_inst_tp = llvm::StructType::get(llvm::getGlobalContext(), obj_fields);
        psymbol_inst_tp = llvm::PointerType::get(symbol_inst_tp,0);
        
        // -- sg_fun_val adaptation --
        funval = getLLVMTypes()->funval;
        pfunval = llvm::PointerType::get(funval,0);
        
        
        listval_tp = getLLVMTypes()->getListVal_IRDef();
        plistval_tp = llvm::PointerType::get(listval_tp,0);
        
    }
    
    // -- lists --
    
    void CodeGen_Types::emit_list_reserve(llvm::Value* list, uint64 n) {
        //if(list->getType() != plistval_tp)
//         plistval_tp->dump();
//         list->getType()->dump();
        AT(list->getType() == plistval_tp, "Wrong type for list");
        
        std::vector<llvm::Value*> args;
        
        args.push_back(list);
        args.push_back(getUInt64Const(n));
        
        builder->CreateCall(f_list_reserve, args);
    }
    
    void CodeGen_Types::emit_list_store_by_index(llvm::Value* list, llvm::Value* obj, uint64 n) {
        AT(list->getType() == plistval_tp, "Wrong type for list");
        obj->getType()->dump();
        AT(obj->getType() == pval, "Wrong type for object");
        
        std::vector<llvm::Value*> args;
        
        args.push_back(list);
        args.push_back(obj);
        args.push_back(getUInt64Const(n));
        
        builder->CreateCall(f_list_store_by_index, args);
    }
    
    void CodeGen_Types::setup_mappings(llvm::ExecutionEngine* EE) {
        EE->addGlobalMapping(f_dump_fun_val, (void*)(sg_dump_fun_val));
        EE->addGlobalMapping(f_check_fvaltype, (void*)(sg_check_fun_val));
        EE->addGlobalMapping(f_list_reserve, (void*)(sg_list_reserve));
        EE->addGlobalMapping(f_list_get_by_index, (void*)(sg_list_get_by_index));
        EE->addGlobalMapping(f_list_store_by_index, (void*)(sg_list_store_by_index));
    }
    
    /// *** CodeGen_LocalSymbols ***
    
    void CodeGen_LocalSymbols::init_internals() {
        // call super
        CodeGen_LocalSymbols_Super::init_internals();
        
        std::vector<llvm::Type*> args;
        
        // f_create_locals
        args.clear();
        // parent
        args.push_back(plocals_tp);
        f_create_locals_tp = llvm::FunctionType::get(plocals_tp, args, false);
        f_create_locals = (llvm::Function*)mod->getOrInsertFunction("__f_create_locals", f_create_locals_tp);
        
        // f_create_sym_inst
        args.clear();
        f_create_sym_inst_tp = llvm::FunctionType::get(psymbol_inst_tp, args, false);
        f_create_sym_inst = (llvm::Function*)mod->getOrInsertFunction("__f_create_sym_inst", f_create_sym_inst_tp);
        
        // f_create_sym_inst_s
        args.clear();
        args.push_back(symcode_tp);
        f_create_sym_inst_s_tp = llvm::FunctionType::get(psymbol_inst_tp, args, false);
        f_create_sym_inst_s = (llvm::Function*)mod->getOrInsertFunction("__f_create_sym_inst_s", f_create_sym_inst_s_tp);
        
        // f_bind_obj_to_sym
        args.clear();
        args.push_back(psymbol_inst_tp);
        args.push_back(pval);
        f_bind_obj_to_sym_tp = llvm::FunctionType::get(void_tp, args, false);
        f_bind_obj_to_sym = (llvm::Function*)mod->getOrInsertFunction("__f_bind_obj_to_sym", f_bind_obj_to_sym_tp);
        
        // f_locals_get_parent
        args.clear();
        args.push_back(plocals_tp);
        f_locals_get_parent_tp = llvm::FunctionType::get(plocals_tp, args, false);
        f_locals_get_parent = (llvm::Function*)mod->getOrInsertFunction("__f_locals_get_parent", f_locals_get_parent_tp);
        
        // f_get_local_obj_by_index
        args.clear();
        args.push_back(plocals_tp);
        args.push_back(i64_tp);
        f_get_local_obj_by_index_tp = llvm::FunctionType::get(pval, args, false);
        f_get_local_obj_by_index = (llvm::Function*)mod->getOrInsertFunction("__f_get_local_obj_by_index", f_get_local_obj_by_index_tp);
        
        // f_get_local_sym_by_index
        args.clear();
        args.push_back(plocals_tp);
        args.push_back(i64_tp);
        f_get_local_sym_by_index_tp = llvm::FunctionType::get(psymbol_inst_tp, args, false);
        f_get_local_sym_by_index = (llvm::Function*)mod->getOrInsertFunction("__f_get_local_sym_by_index", f_get_local_sym_by_index_tp);
        
        // f_store_local_by_index
        args.clear();
        args.push_back(plocals_tp);
        args.push_back(pval);
        args.push_back(i64_tp);
        f_store_local_by_index_tp = llvm::FunctionType::get(psymbol_inst_tp, args, false);
        f_store_local_by_index = (llvm::Function*)mod->getOrInsertFunction("__f_store_local_by_index", f_store_local_by_index_tp);
        
    }
    
    void CodeGen_LocalSymbols::createTypes() {
        CodeGen_Types::createTypes();
        
        std::vector<llvm::Type*> obj_fields;
        
        // -- sg_locals_val adaptation --
        obj_fields = obj_header;
        
        // pointer to parent
        obj_fields.push_back(pvoid);
        // vars
        locals_list_offset = obj_fields.size();
        obj_fields.push_back(plistval_tp);
        
        locals_tp = llvm::StructType::get(llvm::getGlobalContext(), obj_fields);
        plocals_tp = llvm::PointerType::get(locals_tp,0);
    }
    
    llvm::Value* CodeGen_LocalSymbols::emit_locals_access_list(llvm::Value* locals) {
        AT(locals->getType() == plocals_tp, "Wrong type for locals");
        
        llvm::Value* pplist = builder->CreateStructGEP(locals, locals_list_offset);
        return builder->CreateLoad(pplist);
    }
    
    llvm::Value* CodeGen_LocalSymbols::emit_create_symbol_instance(sg_name_val* m) {
        std::vector<llvm::Value*> args;
        
        args.push_back(getConstSym(m->s));
        
        return builder->CreateCall(f_create_sym_inst_s, args, "syminst_" + sg_name_from_sym(m->s, st));
    }
    
    llvm::Value* CodeGen_LocalSymbols::emit_create_locals(sg_fctx_val* z, llvm::Value* parent) {
        std::vector<llvm::Value*> args;
        
        if(parent) {
            AT(parent->getType() == plocals_tp, "Unexpected type for parent");
            args.push_back(parent);
        } else
            args.push_back(llvm::ConstantExpr::getBitCast(getPValNull(), plocals_tp));
        
        llvm::Value* locals = builder->CreateCall(f_create_locals, args, "locals");
        
        llvm::Value* list = emit_locals_access_list(locals);
        
        // reserve for pre-detected symbols
        emit_list_reserve(list, z->n);
        
        // emit the symbol instances
        for(uint64 i=0; i<z->n; ++i) {
            llvm::Value* syminst = emit_create_symbol_instance(z->m[i]);
            emit_list_store_by_index(list, castToPVal(syminst), i);
        }
        
        return locals;
    }
    
    // fetch symbol
    llvm::Value* CodeGen_LocalSymbols::fetch_symbol(sg_name_val* m, sg_fctx_val* z, llvm::Value* locals) {
        AT(locals->getType() == plocals_tp, "Unexpected type for locals");
        SG_WRITE(2, "fetch_symbol ");
        symtabPrintSymK(2, st, m->s);
        SG_WRITE(2, "\n");
        
        if(m->ctx == z) {
            SG_WRITE(2, "current context\n");
            
            std::vector<llvm::Value*> args;
            
            args.push_back(locals);
            args.push_back(getUInt64Const(m->pos));
            
            return builder->CreateCall(f_get_local_sym_by_index, args);
        } else {
            SG_WRITE(2, "deeper context\n");
            AN(z->par, "Reached level 0 context");
            
            std::vector<llvm::Value*> args;
            
            args.push_back(locals);
            
            llvm::Value* parent_ctx = builder->CreateCall(f_locals_get_parent, args);
            
            return fetch_symbol(m, z->par, parent_ctx);
        }
        
//         return NULL; // to prevent warnings
    }
    
    void CodeGen_LocalSymbols::emit_store_local(llvm::Value* locals, llvm::Value* obj, sg_name_val* m) {
        AT(locals->getType() == plocals_tp, "Unexpected type for locals");
        
        std::vector<llvm::Value*> args;
        
        args.push_back(locals);
        args.push_back(obj);
        args.push_back(getUInt64Const(m->pos));
        
        builder->CreateCall(f_store_local_by_index, args);
    }
    
    // bind a symbol to an object
    void CodeGen_LocalSymbols::emit_bind_obj_to_sym(llvm::Value* syminst, llvm::Value* obj, std::string symname) {
        AT(syminst->getType() == psymbol_inst_tp, "Expected symbol instance");
        AT(obj->getType() == pval, "Expected object");
        // fast way
        //builder->CreateStore(obj, builder->CreateStructGEP(syminst, val_header_off+SG_SYMBOL_INST_OBJ_OFF));
        // informative way
        std::vector<llvm::Value*> args;
        args.push_back(syminst);
        args.push_back(obj);
        builder->CreateCall(f_bind_obj_to_sym, args); // "bind_obj_to_sym_" + symname
    }
    
    void CodeGen_LocalSymbols::setup_mappings(llvm::ExecutionEngine* EE) {
        CodeGen_LocalSymbols_Super::setup_mappings(EE);
        
        EE->addGlobalMapping(f_create_locals, (void*)(sg_create_locals));
        EE->addGlobalMapping(f_create_sym_inst, (void*)(sg_create_symbol_inst));
        EE->addGlobalMapping(f_create_sym_inst_s, (void*)(sg_create_symbol_inst_s));
        EE->addGlobalMapping(f_bind_obj_to_sym, (void*)(sg_bind_obj_to_symbol));
        EE->addGlobalMapping(f_locals_get_parent, (void*)(sg_locals_get_parent));
        EE->addGlobalMapping(f_get_local_sym_by_index, (void*)(sg_locals_get_sym_by_index));
        EE->addGlobalMapping(f_get_local_obj_by_index, (void*)(sg_locals_get_obj_by_index));
        EE->addGlobalMapping(f_store_local_by_index, (void*)(sg_locals_store_by_index));
    }
    
    llvm::FunctionType* CodeGen_UserFun::make_fun_tp_from_call(uint64 nargs) {
        uint64 i;
        llvm::Type* ret_tp = pval;
        std::vector<llvm::Type*> args;
        
        args.push_back(plocals_tp);
        
        for(i=0;i<nargs;++i)
            args.push_back(pval);
        
        return llvm::FunctionType::get(ret_tp, args, false);
    }
    
    llvm::FunctionType* CodeGen_UserFun::make_fun_tp(compiledFun* f) {
        // parent context
//         sg_fctx_val* pctx;
        uint64 i;
        std::vector<llvm::Type*> args;
        // function returns void?
        //llvm::Type* ret_tp = (f->fv->vd) ? void_tp : pvoid;
        llvm::Type* ret_tp = pval;
        
//         pctx = f->ctx->par;
//         AN(pctx, "Need parent context");
//         AN(pctx->ls_locals, "Need parent context locals");
//         args.push_back(llvm::PointerType::get((llvm::StructType*)pctx->ls_locals, 0));
        // context
        args.push_back(plocals_tp);
        for(i=0;i<f->fv->nargs;++i)
            args.push_back(pval);
        return llvm::FunctionType::get(ret_tp, args, false);
    }
    
    /*llvm::Value* CodeGen_Types::getSymbolBoundObj(sg_name_val* m) {
        
    }*/
    
    llvm::Function::arg_iterator get_arg(llvm::Function* f, uint64 n) {
        llvm::Function::arg_iterator a=f->arg_begin();
        while(n) {
            ++a;
            --n;
        }
        return a;
    }
    
    /// *** CodeGen_Toplevel ***
    
    CodeGen_Toplevel::CodeGen_Toplevel(sg_module* m) {
        msrc = m;
        st = m->st;
        
        llvm::LLVMContext &Context = llvm::getGlobalContext();
        mod = new llvm::Module("module_name", Context);
        
        // create builder
        builder = new llvm::IRBuilder<>(llvm::getGlobalContext());
        
    }
    
    CodeGen_Toplevel::~CodeGen_Toplevel() {
        delete mod;
        delete builder;
    }
    
    llvm::Type* CodeGen_Toplevel::get_builtin_fun_ret_tp(sg_fun_val* f) {
        if(f->vd)
            return llvm::Type::getVoidTy(llvm::getGlobalContext());
        else
            return pval;
    }
    
    void CodeGen_Toplevel::init_internals() {
        // delegate
        Super::init_internals();
        
        install_builtin_funs(sg_global_session()->bf);
    }
    
    void CodeGen_Toplevel::install_builtin_funs(builtin_fun_ctx* bf) {
        std::vector<llvm::Type*> args;
        
        // empty
        SG_WRITE(2, "Installing bf_empty\n");
        args.clear();
        args.push_back(pvoid);
        bf->empty->lft = llvm::FunctionType::get(pval, args, false);
        bf->empty->lf = (llvm::Function*)mod->getOrInsertFunction("empty", (llvm::FunctionType*)bf->empty->lft);
        SG_WRITE(2, "Installing bf_empty2\n");
        funVal_StoreFP(bf->empty, (llvm::Value*)bf->empty->lf);
        SG_WRITE(2, "Done installing bf_empty\n");
        // create gl (internal; not accessible by user)
        #if 0
        f_create_gl = llvm::cast<llvm::Function*>(
            mod->getOrInsertFunction("__f_create_gl", pval /* ret */, pvoid, pval, symcode_tp, NULL));
        #endif
        
        // fmalloc
        args.clear();
        args.push_back(i64_tp);
        fmalloct = llvm::FunctionType::get(pvoid, args, false);
        fmalloc = (llvm::Function*)mod->getOrInsertFunction("fmalloc", fmalloct);
        
        // f_do_call0
        args.clear();
        args.push_back(pval);
        f_do_call0_tp = llvm::FunctionType::get(pval, args, false);
        f_do_call0 = (llvm::Function*)mod->getOrInsertFunction("__f_do_call0", f_do_call0_tp);
        
        // f_do_call1
        args.clear();
        args.push_back(pval);
        args.push_back(pval);
        f_do_call1_tp = llvm::FunctionType::get(pval, args, false);
        f_do_call1 = (llvm::Function*)mod->getOrInsertFunction("__f_do_call1", f_do_call1_tp);
        
        // f_do_assn
        args.clear();
        args.push_back(ppval);
        args.push_back(pval);
        f_do_assn_tp = llvm::FunctionType::get(pval, args, false);
        f_do_assn = (llvm::Function*)mod->getOrInsertFunction("__f_do_assn", f_do_assn_tp);
        
        // f_do_faccessl
        args.clear();
        args.push_back(pval);
        args.push_back(symcode_tp);
        f_do_faccessl_tp = llvm::FunctionType::get(ppval, args, false);
        f_do_faccessl = (llvm::Function*)mod->getOrInsertFunction("__f_do_faccessl", f_do_faccessl_tp);
        
        // f_create_int
        args.clear();
        args.push_back(i64_tp);
        f_create_int_tp = llvm::FunctionType::get(pval, args, false);
        f_create_int = (llvm::Function*)mod->getOrInsertFunction("__f_create_int", f_create_int_tp);
        
        // f_create_uint
        args.clear();
        args.push_back(i64_tp);
        f_create_uint_tp = llvm::FunctionType::get(pval, args, false);
        f_create_uint = (llvm::Function*)mod->getOrInsertFunction("__f_create_uint", f_create_uint_tp);
        
        // f_create_str
        args.clear();
        args.push_back(pvoid);
        args.push_back(i64_tp);
        f_create_str_tp = llvm::FunctionType::get(pval, args, false);
        f_create_str = (llvm::Function*)mod->getOrInsertFunction("__f_create_str", f_create_str_tp);
        
        // f_rt_call_check
        args.clear();
        args.push_back(pfunval);
        args.push_back(i64_tp);
        f_rt_call_check_tp = llvm::FunctionType::get(void_tp, args, false);
        f_rt_call_check = (llvm::Function*)mod->getOrInsertFunction("__f_rt_call_check", f_rt_call_check_tp);
        
        // f_dump_uint64
        args.clear();
        args.push_back(i64_tp);
        f_dump_uint64_tp = llvm::FunctionType::get(void_tp, args, false);
        f_dump_uint64 = (llvm::Function*)mod->getOrInsertFunction("__f_dump_uint64", f_dump_uint64_tp);
    }
    
    void CodeGen_Toplevel::do_fprint_int(uint64 i) {
        std::vector<llvm::Value*> args;
        args.push_back(getUInt64Const(i));
        builder->CreateCall(f_dump_uint64, args);
    }
            
    // -- Utils
    
    std::string CodeGen_Toplevel::name_from_sym(symcode s) {
        /*char* str;
        str = sg_lex_buf2str(symtab_namek(st, s));
        std::string name(str);
        free(str);
        return name;*/
        return sg_name_from_sym(s, st);
    }
    
//     llvm::StructType* CodeGen_Toplevel::get_locals_type(sg_fctx_val* z) {
//         if(z->ls_locals)
//             return (llvm::StructType*)z->ls_locals;
//         std::vector<llvm::Type*> fields;
//         
//         // add pointer to parent context
//         fields.push_back(pvoid);
//         
//         for(uint64 i=0; i<z->n; ++i) {
//             z->m[i]->pos = fields.size();
//             fields.push_back(pval);
//         }
//         llvm::StructType* tp = llvm::StructType::get(llvm::getGlobalContext(), fields);
//         z->ls_locals = tp;
//         return tp;
//     }
    
//     void CodeGen_Toplevel::instance_symbol(compiledSrc* c, sg_name_val* m, llvm::Value* pscope) {
//         AN(0, "DEPRECATED");
//         // allocate
//         m->syminst = builder->CreateAlloca(symbol_inst_tp, NULL, sg_name_val_rep(m, st));
//         // set symbol
//         //SG_WRITE(2, "setting local symbol\n");
//         llvm::Value* psym = builder->CreateStructGEP((llvm::Value*)m->syminst, val_header_off+SG_SYMBOL_INST_SYM_OFF);
//         //psym->getType()->dump();
//         builder->CreateStore(getConstSym(m->s), psym);
//         // set obj ptr to null
//         //SG_WRITE(2, "setting local obj ptr to null\n");
//         llvm::Value* pobj = builder->CreateStructGEP((llvm::Value*)m->syminst, val_header_off+SG_SYMBOL_INST_OBJ_OFF);
//         builder->CreateStore(getConstPVal(NULL), pobj);
//         
//         //write the value to scope
//         SG_WRITE(2, "m->pos = ");
//         sg_writeUInt64(2, m->pos);
//         SG_WRITE(2, "\n");
//         llvm::Value* local = builder->CreateStructGEP(pscope, m->pos);
//         llvm::Value* inst_val = builder->CreateBitCast((llvm::Value*)m->syminst, pval);
//         builder->CreateStore(inst_val, local);
//     }
    
//     void CodeGen_Toplevel::instance_symbol(llvm::Value* locals) {
//         AT(locals->getType() == plocals_tp, "Unexpected type for locals");
//     }
    
    llvm::Value* CodeGen_Toplevel::emit_get_sym_inst_sym(llvm::Value* syminst) {
        return builder->CreateLoad(builder->CreateStructGEP(syminst, val_header_off+SG_SYMBOL_INST_SYM_OFF));
    }
    
    llvm::Value* CodeGen_Toplevel::emit_get_sym_pobj(llvm::Value* syminst) {
        AT(syminst->getType() == psymbol_inst_tp, "Wrong type for syminst");
        return builder->CreateLoad(builder->CreateStructGEP(syminst, val_header_off+SG_SYMBOL_INST_OBJ_OFF));
    }
    
    llvm::Value* CodeGen_Toplevel::emit_symbol_instance_get_ppobj(llvm::Value* syminst) {
        AT(syminst->getType() == psymbol_inst_tp, "Wrong type for syminst");
        return builder->CreateStructGEP(syminst, val_header_off+SG_SYMBOL_INST_OBJ_OFF);
    }
    
    // -- Function emission --
    
    void CodeGen_Toplevel::emit_funs(compiledSrc* c) {
        for(uint64 i=0; i<c->nf; ++i) {
            // get it
            compiledFun* f = c->f[i];
            
            llvm::IRBuilderBase::InsertPoint ip = builder->saveIP();
            // emit it
            emit_fun(c,f->ctx,f);
            
            builder->restoreIP(ip);
        }
    }
    
    llvm::Value* CodeGen_Toplevel::emit_fun(compiledSrc* c, sg_fctx_val* z, compiledFun* f) {
        char* fname;
        compilerMsg(c, "Emitting function %sym, nargs = %ul\n", f->s, f->fv->nargs);
        llvm::Function* ff;
        sg_fun_val* fv = f->fv;
        // type
        llvm::FunctionType* ft = make_fun_tp(f);
        // set type in fun value
        fv->lft = ft;
        // builtin function object
        
        
        fname = sg_lex_buf2str(symtab_namek(st, f->s));
        ff = llvm::Function::Create(ft, llvm::Function::ExternalLinkage, fname, mod);
        fv->lf = (void*)ff;
        
        // *var init*
        // store fun pointer into fun val
        funVal_StoreFP(fv, ff);
        // store fun val into locals
        emit_bind_obj_to_sym(fetch_symbol(f->m, z->par, (llvm::Value*)z->par->vlocals2), getConstPVal((sg_value*)fv), sg_name_from_sym(f->m->s, st));
        
        llvm::BasicBlock *BB = llvm::BasicBlock::Create(llvm::getGlobalContext(), "entry", ff);
        builder->SetInsertPoint(BB);
        
        // *locals*
        
        // get parent context
        AN(z->par, "Need parent context of fun");
        AN(z->par->vlocals2, "Need parent locals");
        ((llvm::Value*)z->par->vlocals2)->dump();
        // emit local context instance
        // DEPRECATED
//         llvm::Value* pscope = builder->CreateAlloca(get_locals_type(z), NULL, "locals");
//         z->vlocals = pscope;
        llvm::Value* locals = emit_create_locals(z, get_arg(ff,0));
        z->vlocals2 = locals;
        
        // builtin params
        sg_name_val* m=f->ctx->m[0];
        // overrun guard
        uint64 i=0;
        for(llvm::Function::arg_iterator a=get_arg(ff,1); a!=ff->arg_end(); ++a, ++m, ++i) {
            AT(i < f->ctx->n, "Argument overrun");
            // push symbol to stack
//             instance_symbol(c,m,pscope);
            
            compilerMsg(c, "Local type for %sym:\n", m->s);
            a->dump();
            std::cout.flush();
            std::cout << "m->syminst = " << m->syminst << "\n";
            ((llvm::Value*)m->syminst)->dump();
            
            // emit store of object (argument) into local symbol:
//             llvm::Value* optr = builder->CreateStructGEP((llvm::Value*)m->syminst, val_header_off+SG_SYMBOL_INST_OBJ_OFF);
            
            
//             std::cout << "optr:\n";
//             optr->dump();
//             std::cout << "optr type:\n";
//             llvm::cast<llvm::PointerType>(optr->getType())->getElementType()->dump();
//             //AN(0);
//             builder->CreateStore(llvm::cast<llvm::Value>(a), optr);
            
            emit_store_local(locals, a, m);
        }
        free(fname);
        // emit locals:
        /*for(uint64 i=0;i<z->n;++i)
            instance_symbol(c, z->m[i]);*/
        
        
        // emit nodes:
        emit_node(c, f->ctx, f->b);
        
        /*if(fv->vd) {
            SG_WRITE(2, "RETURNING VOID FOR FUN\n");
        }*/
        builder->CreateRet(getPValNull());
        
        //SG_WRITE(2, "module after function codegen:\n");
        //mod->dump();
        
        llvm::verifyFunction(*ff);
        
        return ff;
    }
    
    void CodeGen_ModImport::init_internals() {
        CodeGen_ModImport_Super::init_internals();
        std::vector<llvm::Type*> args;
        
        // f_do_faccessr
        args.clear();
        args.push_back(pval);
        args.push_back(symcode_tp);
        f_do_faccessr_tp = llvm::FunctionType::get(pval, args, false);
        f_do_faccessr = (llvm::Function*)mod->getOrInsertFunction("__f_do_faccessr", f_do_faccessr_tp);
        
        // f_fetch_mod
        args.clear();
        args.push_back(pval);
        args.push_back(symcode_tp);
        f_fetch_mod_tp = llvm::FunctionType::get(pval, args, false);
        f_fetch_mod = (llvm::Function*)mod->getOrInsertFunction("__f_fetch_mod", f_fetch_mod_tp);
    }
    
    llvm::Value* CodeGen_ModImport::emit_import_expr(compiledSrc* c, sg_fctx_val* z, parserExp* x, llvm::Value* obj) {
        AN(x->type == PAST_NODE_TYPE_SYMBOL || PAST_NODE_TYPE_FACCESS, "Wrong type for import expression");
        
        std::vector<llvm::Value*> args;
        
        if(obj == NULL) {
            args.push_back(getConstPVal((sg_value*)sg_global_session()->ml)); // mod loader (safe to cast to void*?)
            args.push_back(getConstSym(x->lvalid)); // symbol
            AN(check_farg_types(f_fetch_mod, args), "Wrong arg signature");
            llvm::Value* modobj = builder->CreateCall(f_fetch_mod, args, "import " + sg_name_from_sym(x->lvalid, c->st));
            if(x->type == PAST_NODE_TYPE_FACCESS)
                return emit_import_expr(c,z,x->a,modobj);
            else
                return modobj;
        } else {
            args.push_back(obj);
            args.push_back(getConstSym(x->lvalid));
            
            return builder->CreateCall(f_do_faccessr, args, "." + sg_name_from_sym(x->lvalid, c->st));
        }
    }
    
    llvm::Value* CodeGen_ModImport::emit_single_import(compiledSrc* c, sg_fctx_val* z, singleImport* g) {
        //SG_WRITE(2, "emit single import\n");
        compilerMsg(c, "Emit single import for %sym:\n", g->sym);
        
        sg_name_val* m = g->m;
        
        // get the module
        std::vector<llvm::Value*> args;
        
        // get the object we're importing (module or object within it)
        llvm::Value* obj = emit_import_expr(c,z,g->exp,NULL);
        
        emit_bind_obj_to_sym(fetch_symbol(m,z,(llvm::Value*)z->vlocals2), obj, sg_name_from_sym(m->s, st));
        
        return NULL;
    }
    
    void CodeGen_ModImport::emit_import(compiledSrc* c, sg_fctx_val* z, importStmt* d) {
        compilerMsg(c, "Emit import\n");
        for(uint64 i=0;i<d->n;++i) {
            emit_single_import(c, z, d->p[i]);
        }
    }
    
    void CodeGen_ModImport::setup_mappings(llvm::ExecutionEngine* EE) {
        CodeGen_ModImport_Super::setup_mappings(EE);
        
        EE->addGlobalMapping(f_fetch_mod, (void*)(sg_fetch_module));
        EE->addGlobalMapping(f_do_faccessr, (void*)(bf_do_faccessr));
    }
    
    llvm::Value* CodeGen_Toplevel::emit_do_assn(llvm::Value* lhs, llvm::Value* rhs) {
        AT(lhs->getType() == ppval, "Wrong type for assn lhs");
        AT(rhs->getType() == pval, "Wrong type for assn rhs");
        
        std::vector<llvm::Value*> args;
        
        args.push_back(lhs);
        args.push_back(rhs);
        
        return builder->CreateCall(f_do_assn, args);
    }
    
    llvm::Value* CodeGen_Toplevel::emit_lval(compiledSrc* c, sg_fctx_val* z, compiledNode* x) {
        
        switch(x->ptype) {
            case PAST_NODE_TYPE_SYMBOL: {
                    sg_name_val* m = (sg_name_val*)x->val;
                    SG_WRITE(2, "Emit lval symbol\n");
                    AT(m->t == (sg_type*)sg_global_session()->bt->name, "Wrong type for symbol");
                    //return builder->CreateStructGEP((llvm::Value*)m->syminst, val_header_off+SG_SYMBOL_INST_OBJ_OFF);
                    return emit_symbol_instance_get_ppobj(fetch_symbol(m, z, (llvm::Value*)z->vlocals2));
                }
            case PAST_NODE_TYPE_FACCESS: {
                SG_WRITE(2, "Emit lval access\n");
                std::vector<llvm::Value*> args;
                
                args.push_back(emit_node(c, z, x->a));
                args.push_back(getConstSym(x->s));
                
                return builder->CreateCall(f_do_faccessl, args, pASTnodeRep(x->p, st));
                }
            default:
                SG_WRITE(2, "Unknown type: ");
                dumpPAST_type(2, x->ptype, 0);
                SG_WRITE(2, "\n");
                dumpCompiledNode(2, x, 0, st, sg_getOpPrec(x->ptype));
                AN(0, "Unknown node");
        }
    }
    
    llvm::Value* CodeGen_Toplevel::emit_node(compiledSrc* c, sg_fctx_val* z, compiledNode* x) {
        //SG_WRITE(2, "module so far:\n");
        //mod->dump();
        SG_WRITE(2, "node:\n");
        dumpCompiledNode(2,x,0,st,sg_getOpPrec(x->ptype));
        SG_WRITE(2, "\n");
        switch(x->ptype) {
            case PAST_NODE_TYPE_BBLOCK_LINK:
                SG_WRITE(2, "Emit BBlock link\n");
                AN(x->a, "Missing link");
                emit_node(c, z, x->a);
                // next
                if(x->b)
                    emit_node(c, z, x->b);
                return NULL;
            case PAST_NODE_TYPE_ASSN: {
                    return emit_do_assn(emit_lval(c,z,x->a), emit_node(c,z,x->b));
                }
            case PAST_NODE_TYPE_SYMBOL: {
                    sg_name_val* m = (sg_name_val*)x->val;
                    AN(m, "No val");
                    AT(m->t == (sg_type*)sg_global_session()->bt->name, "Value is not a name as expected");
                    SG_WRITE(2, "Emit symbol\n");
                    if(m->ctx)
                        // context is defined
                        return emit_get_sym_pobj(fetch_symbol(m,z,(llvm::Value*)z->vlocals2));
                    else
                        // no context - builtin
                        return getConstPVal(m->o);
                }
            case PAST_NODE_TYPE_FACCESS: {
                    SG_WRITE(2, "Emit rval faccess\n");
                    std::vector<llvm::Value*> args;
                    
                    args.push_back(emit_node(c, z, x->a));
                    // FIXED: confined to single-tier lvals
                    args.push_back(getConstSym(x->s));
                    
                    return builder->CreateCall(f_do_faccessr, args);
                }
            case PAST_NODE_TYPE_CALL: {
                    compiledNode* xx;
                    std::vector<llvm::Value*> args;
                    SG_WRITE(2, "emit CALL\n");
                    
                    // pass context
//                     llvm::Value* lcontext_simple;
//                     if(z->par) {
//                         llvm::Value* lcontext = (llvm::Value*)(z->vlocals);
//                         lcontext_simple = builder->CreateStructGEP(lcontext, 0);
//                     } else {
//                         llvm::Value* lcontext = (llvm::Value*)(z->vlocals);
//                         lcontext_simple = builder->CreateBitCast(lcontext, pvoid);
//                     }
                    do_fprint_int(gcounter++);
                    
                    // only pass global context to functions
                    args.push_back((llvm::Value*)c->g->vlocals2);
                    
                    // if a function is a member method, pass self
                    if(x->a->ptype == PAST_NODE_TYPE_FACCESS)
                        args.push_back(emit_node(c, z, x->a->a));
                    
                    if(x->b) {
                        xx = x->b;
                        while(xx) {
                            AT(xx->ptype == PAST_NODE_TYPE_RVAL_LINK, "Expected rval link");
                            args.push_back(emit_node(c,z,xx->a));
                            std::cout << "arg type for " << pASTnodeRep(xx->a->p, st) << ":\n";
                            args.back()->getType()->dump();
                            xx = xx->b;
                        }
                    }
                    
                    SG_WRITE(2, "Load fp\n");
                    do_fprint_int(gcounter++);
                    
                    llvm::Value* fn;
                        
                    do_fprint_int(gcounter++);
                    llvm::Value* fval=castToFunVal(emit_node(c, z, x->a));
                    fn = getFunValInlineFPtr(fval, make_fun_tp_from_call(args.size()-1));
                    AN(check_farg_types((llvm::Function*)fn, args), "Wrong arg signature");
                    
                    do_fprint_int(gcounter++);
                    
                    std::vector<llvm::Value*> rtargs;
                    rtargs.push_back(fval);
                    // if a function is a member method, pass self
                    if(x->a->ptype == PAST_NODE_TYPE_FACCESS)
                        rtargs.push_back(getUInt64Const(args.size()-2));
                    else
                        rtargs.push_back(getUInt64Const(args.size()-1));
                    builder->CreateCall(f_rt_call_check, rtargs);
                
                    return builder->CreateCall(fn, args, pASTnodeRep(x->p, st));
                }
            case PAST_NODE_TYPE_UINT: {
                    SG_WRITE(2, "emit uint\n");
                    std::vector<llvm::Value*> args;
                    args.push_back(getUInt64Const(x->p->v.u));
                    return builder->CreateCall(f_create_uint, args);
                }
            case PAST_NODE_TYPE_STR: {
                    SG_WRITE(2, "emit str\n");
                    std::vector<llvm::Value*> args;
                    args.push_back(getConstVoidPtr((void*)x->p->v.str.w));
                    args.push_back(getUInt64Const(x->p->v.str.l));
                    return builder->CreateCall(f_create_str, args);
                }
                /*
            case PAST_NODE_TYPE_FACCESS: {
                    sg_name_val* m;
                    SG_WRITE(2, "Emit faccess\n");
                    AN(x->a, "Missing feature");
                    args.reserve(3); // these things tend to be unreliable...
                    // arguments
                    // addr of object (pvar type)
                    m = bf_fctx_get_local(z, x->s);
                    AN(m, "Symbol undefined");
                    args.push_back((llvm::Value*)m->lxp);
                    // symbol
                    args.push_back(llvm::ConstantInt::get(symcode_tp, x->a->s));
                    return builder->CreateCall(f_create_gl, args);
                }*/
            case PAST_NODE_TYPE_IMPORT: {
                emit_import(c, z, x->p->v.imp);
                return NULL;
            }
            default:
                SG_WRITE(2, "Unknown type: ");
                dumpPAST_type(2, x->ptype, 0);
                SG_WRITE(2, "\n");
                dumpCompiledNode(2, x, 0, st, sg_getOpPrec(x->ptype));
                AN(0, "Unknown node");
        }
    }
    
    void CodeGen_Toplevel::generate() {
            
        createTypes();
        
        std::vector<llvm::Type*> args;
        
        // init builtins
        llvm::FunctionType *f_init_builtins_t = llvm::FunctionType::get(llvm::Type::getVoidTy(llvm::getGlobalContext()), args, false);
        llvm::Function* f_init_builtins = llvm::Function::Create(f_init_builtins_t, llvm::Function::ExternalLinkage, "__f_init_builtins", mod);
        {
            llvm::BasicBlock *BB = llvm::BasicBlock::Create(llvm::getGlobalContext(), "entry", f_init_builtins);
            builder->SetInsertPoint(BB);
            init_internals();
        }
        
        builder->CreateRetVoid();
        llvm::verifyFunction(*f_init_builtins);
        
        // generate __main
        {
            args.clear();
            llvm::FunctionType *FT = llvm::FunctionType::get(llvm::Type::getVoidTy(llvm::getGlobalContext()), args, false);
            fmain = llvm::Function::Create(FT, llvm::Function::ExternalLinkage, "__main", mod);
            llvm::BasicBlock *BB = llvm::BasicBlock::Create(llvm::getGlobalContext(), "entry", fmain);
            builder->SetInsertPoint(BB);
            
            // emit locals for main:
            // llvm::Value* locals = builder->CreateAlloca(get_locals_type(msrc->src->g), NULL, "locals");
            llvm::Value* locals = emit_create_locals(msrc->src->g, NULL);
            msrc->src->g->vlocals2 = locals;
            
            // emit source-level functions, initializing local vars
            emit_funs(msrc->src);
            
            // call init builtins
            builder->CreateCall(f_init_builtins);
            // don't have to initialize toplevel locals; they are initted to zero
            
            // emit nodes:
            emit_node(msrc->src, msrc->src->g, msrc->src->x);
            
            builder->CreateRetVoid();
            
            llvm::verifyFunction(*fmain);
        }
        
        mod->dump();
    }
    
    void CodeGen_Toplevel::setup_mappings(llvm::ExecutionEngine* EE) {
        // super
        Super::setup_mappings(EE);
        
        // global mappings
        EE->addGlobalMapping(fmalloc, (void*)(&malloc));
        EE->addGlobalMapping((llvm::Function*)sg_global_session()->bf->empty->lf, (void*)(sg_global_session()->bf->empty->f));
        EE->addGlobalMapping(f_do_call0, (void*)(&bf_do_call0));
        EE->addGlobalMapping(f_do_call1, (void*)(bf_do_call1));
        EE->addGlobalMapping(f_do_assn, (void*)(bf_do_assn));
        EE->addGlobalMapping(f_do_faccessl, (void*)(bf_do_faccessl));
        EE->addGlobalMapping(f_create_int, (void*)(sg_create_int));
        EE->addGlobalMapping(f_create_uint, (void*)(sg_create_uint));
        EE->addGlobalMapping(f_create_str, (void*)(sg_create_str));
        EE->addGlobalMapping(f_rt_call_check, (void*)(sg_rt_call_check));
        EE->addGlobalMapping(f_dump_uint64, (void*)(sg_dump_uint64));
    }
    
    void CodeGen_Toplevel::run() {
        llvm::InitializeNativeTarget();
        llvm::InitializeNativeTargetAsmPrinter();
//         LLVMLinkInMCJIT();
//         LLVMLinkInJIT();
        // create ee
        //llvmi->setee(llvm::MCJIT::create(mod, false));
        //llvm::ExecutionEngine* EE = llvmi->getee();
        llvm::EngineBuilder EB(mod);
//         EB.setUseMCJIT(true);
        
        llvm::ExecutionEngine* EE = EB.create();
        // setup function mappings
        setup_mappings(EE);
        // run the function
        //std::vector<llvm::GenericValue> noargs;
        //EE->runFunction(fmain, noargs);
        void (*fp_main)();
        fp_main = (void (*)())EE->getPointerToFunction(fmain);
        fp_main();
    }
    
}
